#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>



//文件操作
/**
* 1.为什么使用文件
* 我们写的程序的数据是存储在电脑的内存中的，如果程序退出，内存回收，数据就丢失了，等再次运行程序，是看不到上次程序的数据的
* 如果要将数据进行持久化的保存，我们可以使用文件。
*
* 2.什么是文件
* 磁盘（硬盘）上的文件就是文件。
* 但是在程序设计中，我们一般谈的文件有两种：程序文件和数据文件（从文件功能的角度来分析）
* 2.1程序文件是程序的源代码等文件.obj,.exe，.c等等
* 2.2数据文件，我们写程序所操作的文件就是数据文件
* 文件的内容不一定是程序，而是程序运行时读写的数据，比如程序运行需要从中读取数据的文件，或者输出内容的文件
* 我们主要讨论数据文件
* 以前处理数据都是以终端为对象的，即从终端的键盘输入数据，运行结果显示到显示屏上
* 其实我们有时候会把信息输入到磁盘上，当需要的时候再从磁盘上把数据读取到内存中使用，这里处理的就是磁盘上的文件
* 2.3文件名
* 一个文件要有一个 唯一 的文件标识，以便于用户识别和引用
* 文件名一般包含3部分：文件路径 + 文件名主干 + 文件后缀
* 例如: c:\code\test.txt
*
* 3.二进制文件和文本文件
* 根据数据的组织形式，数据文件被称为文本文件或者二进制文件
* 数据再内存中以二进制的形式存储，如果不加转换的就输出到外存/因硬盘的文件中，就是 二进制文件
* 如果要求在外存上以ASCII码的形式存储，则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
* 一个数据在文件中是怎么存储的呢？
* 字符一律以ASCII码的的形式存储，数值型数据既可以用ASCII形式存储，也可以使用二进制形式存储
* eg：
* 如果有整数10000，如果以ASCII码的形式00110001(1) 00110000(0) 001100000(0) 001100000(0) 001100000(0)输出到磁盘，则磁盘中会占用5个字节（每一个字符一个字节），
* 而以二进制形式存储，000000000 000000000 00100111 00010000只占4个字节
*/
//测试代码
//int main()
//{
//	int a = 10000;
//	FILE* pf = fopen("text.txt", "wb");
//	fwrite(&a, 4, 1, pf);
//	//小端存储
//	//00010000 00100111 00000000 000000000
//	//10       27       00       00   
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}

/**
* 4.文件的打开和关闭
* 4.1流和标准流
* 4.1.1流
* 我们程序的数据需要输出到各种外部的设备，也需要从外部设备获取数据，不同的外部设备的输入输出操作各不相同，为了方便程序员对各种设备进行方便的操作
* 抽象出了流的概念。C程序针对文件，画面，键盘的数据输入输出操作都是通过流操作的。
* 一般情况下，我们要想想流里写数据，或者从流里面读取数据，都是要先打开流，然后操作。
* 4.1.2标准流
* 那为什么我们从键盘输入数据，向屏幕上输出数据，并没有打开流呢？
* C语言程序在启动的时候，默认打开了3个流：
* stdin - 标准输入流，在大多数环境中从键盘输入，scanf函数就是从标准输入流中读取数据。
* stdout - 标准输出流，大多数环境中输出到显示器界面，printf函数就是将细腻些输出到标准输出流中。
* stderr - 标准错误流，大多数环境中输出到显示器界面。
* 这是默认打开了这三个流，我们使用scanf,printf等函数就可以直接进行输入输出操作的。
* stdin.stdout.stderr这三个流的类型是：FILE*，是一种结构体指针，FILE是一种结构体，通常称之为文件指针
* C语言程序中，就是通过FILE*的文件指针来维护流的各种操作的
*
* 4.2文件指针
* 每个被使用的文件都在内存中开辟了一个相应的文件信息区，用来存放文件的相关信息（如文件的名字，文件状态意见文件当前的位置等）。
* 这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的，取名为FILE。
* 不同的C编译器的FILE类型包含的内容不完全相同，但是大同小异
* 每当打开一个文件的时候，系统就会根据文件的情况 自动创建一个FILE结构的变量，并填充其中的信息，使用者不需要关系细节
* 一般都是通过一个FILE的指针来维护这个FILE结构的变量
* //创建一个FILE* 的指针变量：
* FILE* pf;
* 定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区域（是一个结构体变量）
* 通过该文件信息区的信息就能够访问该文件。
* 也就是说，通过文件指针变量能够间接找到与它关联的文件
*
* 4.3文件的打开和关闭
* 文件在读写之前应该先打开文件，在使用结束后应该关闭文件
* 打开文件->读/写文件->关闭文件
* 在编写程序的时候，打开文件的同时，就会返回一个FILE*的指针变量，来指向该文件，也相当于建立了指针和文件的联系
* ANSIC规定使用fopen函数来打开文件，fclose来关闭文件
* //注意：只要打开文件就会返回文件信息区的地址
*
*
* 打开文件
*                         文件名                打开方式
* FILE* fopen(const char* filename, const char* mode);
*
* 关闭文件
* FILE* fclose(FILE* stream);//fclose不会将stream置为空，所以需要stream = NULL
*                写（输出）
*   程序       ==========>     文件
* （内存中）    <==========
*                读（输入）
*
*/

//int main()
//{
//	//打开文件
//	FILE* pf = fopen("text.txt", "w");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	//读文件
//	//...
//
//
//	//关闭文件
//	fclose(pf);//不会将pf置为空，所以需要pf = NULL;
//	pf = NULL;
//
//	return 0;
//}

/**
* mode表示文件的打开模式
* 注意，打开方式都是指向一个字符串，所以是用""包含的
*
* 文件使用方式            含义                             如果指定文件不存在
* "r"(只读)     为了输入数据，打开一个已经存在的文本文件         出错（返回空指针）
* "w"(只写)     为了输出数据，打开一个文本文件(如果该文本        建立一个新的文件
*              文件之前就已经有内容，则会将之前的内容情况)
* "a"(追加)    向文本文件的末尾添加数据                        建立一个新的文件
*/
//int main()
//{
//	FILE* ps = fopen("test.text", "w");
//
//	fclose(ps);
//	ps = NULL;
//	return 0;
//}
//
//int main()
//{
//	FILE* pFILE;
//	pFILE = fopen("test.text", "w");
//	if (pFILE != NULL)
//	{
//		fputs("fopen example", pFILE);
//		fclose(pFILE);
//		pFILE = NULL;
//	}
//	return 0;
//}

/**
* 5.文件的顺序读写（注意：每调用一次函数，该文件光标也会自动向后一个字节来进行维护）
* 函数名           功能                   适用于
* fgetc         字符输入函数            所有输入流  （包括文件流，标准输入流 - stdin）
* fputc         字符输出函数            所有输出流   (包括文件流，标准输出流 - stdout)
* fgets         文本行输入函数          所有输入流
* fputs         文本行输入函数          所有输出流
* fscanf        格式化输入函数          所有输入流
* fprintf       格式化输出函数          所有输出流
* fread         二进制输入             文件输入流
* fwrite        二进制输出             文件输出流
*/

//fputc function
//函数原型
//int fputc(int character, FILE* stream);
//将 str 指向的 C 字符串写入流。
//将字符写入流并前进位置指示器。
//字符被写入流的内部位置指示器指示的位置，然后自动前进 1。
//
//成功后，将返回写入的字符。
//如果发生写入错误，则返回 EOF(-1) 并设置错误指示符 （ferror）。
// 
// 

//int main()
//{
//	FILE* pf;
//	char c;
//    pf = fopen("test.text", "w");
//	if (pf != NULL)
//	{
//		for (c = 'a'; c <= 'z'; c++)
//		{
//			fputc(c, pf);
//		}
//		fputc('\n', pf);
//		fputc('A', pf);
//		fclose(pf);
//		pf = NULL;
//	}
//	return 0;
//}

//fgetc funtion
//函数原型
//int fgetc(FILE* stream);
//返回指定流的内部文件位置指示器当前指向的字符。然后，内部文件位置指示器前进到下一个字符。
//如果调用时流位于文件末尾，则函数将返回 EOF 并设置流的文件结束指示器 （feof）。
//如果发生读取错误，该函数将返回 EOF 并为流设置错误指示符 （ferror）。

//成功后，将返回字符 read（提升为 int 值）。
//返回类型为 int 以容纳特殊值 EOF，该值指示失败：
//如果位置指示符位于文件末尾，则函数返回 EOF 并设置 stream 的 eof 指示符 （feof）。
//如果发生其他读取错误，该函数还会返回 EOF，但会设置其错误指示符 （ferror）。

//int main()
//{
//	FILE* pf = fopen("test.text", "r");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	int ch = 0;
//	while ((ch = fgetc(pf)) != EOF)
//	{
//		printf("%c ", ch);
//	}
//	fclose(pf);
//	pf = NULL;
//	return 0;
//}

//fputs函数
//函数原型
//int fputs(const char* str, FILE* stream);
//将str指向的C字符串写入流
//该函数从指定的地址(str)开始赋值，知道到达终止字符(\0),
//\0不会复制到流中
//注意，fputs不会写入其他字符，不会在末尾自动附加换行符
//成功后，返回非负值
//出错会返回EOF

//int main()
//{
//	FILE* pf;
//	//打开文件
//	pf = fopen("test.text", "w");
//	
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//
//	//写文件
//	fputs("hello world\n", pf);
//	fputs("zzr", pf);
//
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}


//fgets函数
//函数原型
//char* fgets(char* str, int num, FILE* stream);
//从stream中获取字符串
//从stream中读取字符并将其作为C字符串存储到str中，直到读取(num - 1)个字符，或者到达换行或文件末尾，停止
//为什么是num - 1个字符呢，因为要留一个字符空间存储\0
//换行符会使fgets停止读取，但它被视为有效字符，并包含在复制到str的字符串中
//终止字符\0会自动附加到字符之后
//成功后，该函数返回str
//如果在尝试读取字符时遇到文件末尾，即没有读到字符，返回空指针
//且str内容不变
//如果发生读取错误，会设置ferror，并返回空指针，但str指向的内容可能已经更改

//int main()
//{
//	//1.打开文件
//	FILE* pf;
//	pf = fopen("test.text", "r");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//
//	//2.操作文件
//	char arr[20] = { 0 };
//
//	while (fgets(arr, 20, pf) != NULL)
//	{
//		printf("%s", arr);
//	}
//	//3.关闭文件
//	fclose(pf);
//	pf = NULL;
//	return 0;
//}



//fprintf函数
//函数原型
//int fprintf(FILE* stream, const char* format, ...);
//对比printf函数
//int printf(const char* format, ...);
//发现只是前面多了一个FILE*的流
//fprintf函数：将格式化数据写入流
//成功后，返回写入的字符总数，如果发生写入错误，则设置(ferror)，并且返回负数
//
//int main()
//{
//	FILE* pFile;
//	int n;
//	char name[100];
//
//	pFile = fopen("test.text", "w");
//	for (n = 0; n < 3; n++)
//	{
//		printf("please,enter a name:");
//		scanf("%s", name);
//		fprintf(pFile, "Name %d [%-10.10s]\n", n + 1, name);
//
//	}
//	return 00;
//}

//
//struct S
//{
//    char name[20];
//    int age;
//    float score;
//};
//
//int main()
//{
//    struct S s = { "张三", 20, 65.5f };
//    //把s中的数据存放在文件中
//    FILE* pf = fopen("test.txt", "w");
//    if (pf == NULL)
//    {
//        perror("fopen");
//        return 1;
//    }
//    fprintf(pf, "%s %d %f", s.name, s.age, s.score);
//
//    fclose(pf);
//    pf = NULL;
//
//    return 0;
//}



//fscanf函数
//函数原型
//int fscanf(FILE* stream, const char* format, ...);
//对比scanf
//int scanf(const char* format, ...);
//从流中读取数据，并根据参数格式将其存储到其他参数指向的位置
//成功后，返回成功填充的参数列表的项目数
//读取错误ferror,返回EOF

//struct S
//{
//	char name[20];
//	int age;
//	float score;
//};
//
//int main()
//{
//	struct S s = { 0 };
//	//想从文件test.txt中读取数据放在s中
//	
//	//打开文件
//	FILE* pf = fopen("test.txt", "r");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	//操作文件
//	//从文件test.txt中读取数据放在s中
//	fscanf(pf, "%s %d %f", s.name, &(s.age), &(s.score));
//
//	//打印在屏幕上
//	printf("%s %d %f\n", s.name, s.age, s.score);
//
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}
//

//fscanf和fprintf都是所有输入输出流
//struct S
//{
//	char name[20];
//	int age;
//	float score;
//};
//
//int main()
//{
//	struct S s = { 0 };
//
//	//操作文件
//	fscanf(stdin, "%s %d %f", s.name, &(s.age), &(s.score)); // == scanf("%s %d %f",s.name,&(s.age),(s.score));
//
//	//打印在屏幕上
//	fprintf(stdout,"%s %d %f\n", s.name, s.age, s.score);
//
//
//	return 0;
//}



//int main()
//{
//	fputc('a', stdout);
//	return 0;
//}


//对比一组函数
//scanf / fscanf/ sscanf
//
// scanf - 从标准输入流(键盘)上读取格式化的数据
//fscanf - 从指定的输入流(也包括标准输入流)上读取格式化的数据
//sscanf - 在字符串中读取格式化的数据

// printf - 把数据以格式化的形式打印在标准输出流(显示屏)上
//fprintf - 把数据以格式化的形式打印在指定的输出流上
//sprintf - 把格式化的数据转化成字符串

//eg：sprintf和sscanf使用
//struct S
//{
//	char name[20];
//	int age;
//	float score;
//};
//
//int main()
//{
//	char buf[200] = { 0 };
//	struct S s = { "张三", 20, 80.5f };
//
//	sprintf(buf, "%s %d %f", s.name, s.age, s.score);
//
//	printf("1.以字符串的形式打印：%s\n", buf);
//
//	struct S t = { 0 };
//	//从结构体变量s中读取数据放在结构体变量t中
//	sscanf(buf, "%s %d %f", t.name, &(t.age), &(t.score));
//
//	printf("2.按照格式打印：      %s %d %f", s.name, s.age, s.score);
//	
//	return 0;
//}

//fwrite函数 -
//函数原型
//size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);
//从流中读取count个元素，每个元素的大小为size字节，并将它们存储到ptr指定的内存块中。
//流的位置指示器按读取的总字节数前进
//如果成功，则读取的总字节数为 size * count
//ptr:指向大小至少为size * count 字节的内存块的指针
//size: 要读取的每个元素的大小(以字节为单位)
//count: 元素数，每个元素的大小为size
//返回成功读取的元素总数
//如果size或者count为0，则返回0，并且ptr指向的内容和流状态保持不变

//
//int main()
//{
//	int arr[] = { 1, 2, 3, 4, 5 };
//	//打开文件
//	FILE* pf = fopen("test.txt", "wb");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	//操作文件
//	size_t sz = sizeof(arr) / sizeof(arr[0]);
//
//	fwrite(arr, sizeof(arr[0]), sz, pf);//以二进制的形式写进去
//	
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}

//fread函数
//函数原型
//size_t fread(void* ptr, size_t size, size_t count, FILE* stream);
//从流中读取count个元素，每个元素的大小为size字节，并把他们存储在ptr指定的内存块中
//流的位置治时期按读取的总字节数前进
//如果成功，则读取的总字节数为size * count
//ptr:指向大小至少为size * count字节的内存块的指针
//size 要读取的每个元素的大小
//count 元素数

//返回成功读取的元素总数


//以二进制的形式读取


//int main()
//{
//	int arr[5] = { 0 };
//
//	//打开文件
//	FILE* pf = fopen("test.txt", "r");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	//操作文件
//	size_t sz = sizeof(arr) / sizeof(arr[0]);
//	fread(arr, sizeof(arr[0]), sz, pf);
//
//	int i = 0;
//	for (i = 0; i < 5; i++)
//	{
//		printf("%d ", arr[i]);
//	}
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}

//int main()
//{
//	int arr[5] = { 0 };
//	FILE* pf = fopen("test.txt", "r");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	int i = 0;
//	while (fread(&arr[i], sizeof(arr[0]), 1, pf))//第一个参数为&arr[i]
//	{
//		printf("%d ", arr[i]);
//		i++;
//	}
//	fclose(pf);
//	pf = NULL;
//	return 0;
//}

//文件的随机读写

//int main()
//{
//	FILE* pf = fopen("test.txt", "r");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	//读文件
//	int ch = fgetc(pf);
//	printf("%c\n", ch);//a
//
//	ch = fgetc(pf);
//	printf("%c\n", ch);//b
//
//	fclose(pf);
//	pf = NULL;
//	
//	return 0;
//}

//fseek函数
//函数原型
//int fseek(FILE* stream, long int offset, int origin);
//根据文件指针的位置和偏移量来定位文件指针
//long int offset:偏移量
//origin:起始位置
//orgin:1.SEEK_SET 文件起始位置
//      2.SEEK_CUR  文件指针的当前位置
//      3.SEEK_END  文件结束位置
//如果成功返回0，否则返回非零值

//int main()
//{
//	FILE* pf = fopen("test.txt", "r");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//
//	}
//	//读文件
//	int ch = fgetc(pf);
//	printf("%c\n", ch);
//	//fseek(pf, 4, SEEK_SET);//e
//	//fseek(pf, 4, SEEK_CUR);//f
//	//fseek(pf, -4, SEEK_END);//d
//	ch = fgetc(pf);
//	printf("%c\n", ch);
//	fclose(pf);
//	pf = NULL;
//	return 0;
//}
// 
// 
//int main()
//{
//	FILE* pFile;
//	pFile = fopen("test.txt", "wb");
//	fputs("This is an apple.", pFile);
//	fseek(pFile, 9, SEEK_SET);
//	fputs(" sam", pFile);
//	fclose(pFile);
//	return 0;
//}


//ftell
//函数原型
//long int ftell(FILE* stream);
//获取流中的当前位置
//成功后，将返回位置指示器的当前值

//int main()
//{
//	FILE* pf = fopen("test.txt", "r");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//
//	}
//	//读文件
//	int ch = fgetc(pf);
//	printf("%c\n", ch);
//	//fseek(pf, 4, SEEK_SET);//e
//	//fseek(pf, 4, SEEK_CUR);//f
//	//fseek(pf, -4, SEEK_END);//d
//	ch = fgetc(pf);
//	printf("%c\n", ch);
//	int ret = ftell(pf);
//	printf("%d\n", ret);
//	fclose(pf);
//	pf = NULL;
//	return 0;
//}


//int main()
//{
//	FILE* pf = fopen("test.txt", "r");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//
//	int ch = fgetc(pf);
//	printf("%c\n", ch);
//	fseek(pf, 0, SEEK_END);//求出文件的长度
//	printf("%d\n", ftell(pf));
//	return 0;
//}



//rewind函数
//函数原型
//void rewind(FILE* stream);
//让文件指针的位置回到文件的起始位置
//
//int main()
//{
//	FILE* pf = fopen("test.txt", "r");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	int i = 0;
//	int ch = 0;
//	for (i = 0; i < 5; i++)
//	{
//		ch = fgetc(pf);
//		printf("%c ", ch);
//	}
//	rewind(pf);
//	ch = fgetc(pf);
//	printf("%c ", ch);
//
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}



//文件读取结束的判定
//feof：并不能判断文件是否结束，是用来判断文件结束的原因
//在文件读取过程中，不能feof函数的返回值来判断文件是否结束
//文件现在读取结束之后，是声明原因文件读取结束了呢？
//1.有可能是遇到了文件末尾 - feof
//2.读取的时候发生了错误 - ferror
//打开一个流的时候，这个流上有两个标记值
//1.是否遇到文件末尾
//2.是否发生了错误

//fgtec 如果读取正常，返回的是读到的字符的ASCII码值
//如果读取的过程中遇到文件末尾，或者发生错误，就返回EOF

//fgets - 如果读取正常，返回的是读到的字符串的字符数组的地址，
//如果读取的过程中遇到文件末尾，或者发生错误，就返回NULL


//int main()
//{
//	FILE* pf = fopen("test.txt", "r");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	//读取
//	int ch = 0;
//	while ((ch = fgetc(pf)) != EOF)
//	{
//		printf("%c", ch);
//	}
//
//	//判断是什么原因导致结束的
//	if (feof(pf))
//	{
//		printf("结尾，正常结束\n");
//	}
//	else if (ferror(pf))
//	{
//		perror("fgetc");
//	}
//
//	return 0;
//}

//拷贝文件 test1.txt -- > test2.txt
//int main()
//{
//	FILE* pfin = fopen("test1.txt", "r");
//	if (pfin == NULL)
//	{
//		perror("fopen:test1.txt");
//		return 1;
//	}
//	FILE* pfout = fopen("test2.txt", "r");
//	if (pfout == NULL)
//	{
//		perror("fopen:test2.txt");
//		return 1;
//	}
//
//	int ch = 0;
//	while ((ch = fgetc(pfin)) != EOF)
//	{
//		fputc(ch, pfout);
//	}
//	if (feof(pfout))
//	{
//		printf("结尾，正常结束\n");
//	}
//	else if (ferror(pfout))
//	{
//		perror("fgetc");
//	}
//	fclose(pfin);
//	pfin = NULL;
//	fclose(pfout);
//	pfout = NULL;
//
//	return 0;
//}



//文件缓冲区
/**
* ANSIC采用"缓冲文件系统"处理数据文件。缓冲文件系统是值系统自动地在内存中为程序中每一个正在使用的文件开辟一块"文件缓冲区"
* 从内存向磁盘输入数据会先送到内存中的缓冲区，待缓冲区装满后才一起送上磁盘。
* 同样的，当从磁盘向计算机读入数据，则从磁盘文件中读取数据输入到内存缓冲区，待充满缓冲区之后，再诸葛地将数据送到程序数据区。
*/


#include <stdio.h>
#include <windows.h>
int main()
{
	FILE* pf = fopen("test.txt", "w");
	fputs("abcdef", pf);//先将代码放在输出缓存区
	printf("睡眠10秒 - 已经写数据了，打开test.txt文件，发现文件没有内容\n");
	Sleep(10000);
	printf("刷新缓冲区\n");
	fflush(pf);//刷新缓冲区的时候，才会将输出缓冲区的数据写到文件（磁盘）中
	printf("再睡眠10秒 - 此时，再打开test.txt，发现文件有内容了\n");
	Sleep(10000);
	//关闭文件fclose的时候，也会刷新缓冲区
	fclose(pf);
	pf = NULL;

	return 0;
}

//结论：因为有缓冲区的存在，C语言在操作文件的时候，需要做刷新缓冲区或者在文件操作结束的时候关闭文件
//如果不做，可能导致读写文件的问题